'''
The notebook consist basically of a gtk.Notebook filled with an
arbitraire amount of worksheets, whereas worksheets consist of many
cells. Cells in turn are not much more than two textfields. An Input
and an Output.
'''

import gtk

flags = {}
#if gtksourceview is not available, a gtk textview is used
#this is controled by the flag 'gtksourceview'
try:
    import gtksourceview
    flags['gtksourceview']=True
except:
    flags['gtksourceview']=False

import os.path, cPickle
from os import listdir, remove
from time import time
from xml.dom import minidom
from xml.sax.saxutils import escape, unescape

from widgetOrganizer import WidgetOrganizer
from dialogs import Dialogs
from plot import Plot

#dialogs are used throughout the whole notebook module. So it
#is instantiated just once.
Dialogs = Dialogs()


class Notebook(WidgetOrganizer, gtk.Notebook):
    '''
    A gtk.Notebook widget that can hold an arbitraire amount of
    worksheets. It is responsible of opening, saving, storing,
    restoring and closing them.
    '''
    
    def __init__(self, ancestor):
        gtk.Notebook.__init__(self)
        WidgetOrganizer.__init__(self, 'Notebook', ancestor)
        
        self.connect('page-reordered', self._event_reorder)
        
        #loads worksheets of the last session
        self.restoreWorksheets()
        
        if not self.children:
            self.addWorksheet()
    
    def _parseXML_(self, path):
        '''
        Parses an xml-document. Returns a DOM-representation
        '''
         
        try:
            DOM = minidom.parse(path)
        except:
            return False
        #TODO: validate input xml befor parsing
        #TODO: give error message if it's not valid
        return DOM      
    
    def _event_reorder(self, notebook, worksheet, pos):
        '''
        Adapts the order of the children list to the tab order.
        '''
        
        self.children.remove(worksheet)
        self.children.insert(pos, worksheet)

    def addWorksheet(self, DOM = None, path = ''):
        '''
        Creates a new worksheet and adds it to the widget tree. If
        a DOM representation is given, it is passed to the worksheet.
        The path defines where this worksheet will be saved on default.
        '''
        
        worksheet = Worksheet(self, DOM, path)
        self.addChild(worksheet, -1)
        self.append_page(worksheet, worksheet.tabWidget)
        
        #focus on the new worksheet
        self.set_current_page(self.page_num(worksheet))
        
        self.set_tab_reorderable(worksheet, True)
        
    def closeWorksheet(self, worksheet):
        '''
        Closes a worksheet if nothing has changed since the last saving.
        Otherwise it opens a dialog to ask if the user want to save
        the file. Depending an the answer either the worksheet is just
        closed, the worksheet is saved, or the closing is aborted. 
        '''
        
        save = None
        if worksheet.hasChanged():
            save = Dialogs.saveSingle(worksheet)
        else:
            self.removeChild(worksheet)
            return
        
        #save is one of: False, True, None
        if save==False:
            self.removeChild(worksheet)
        elif save and self.saveWorksheet(worksheet):
            self.removeChild(worksheet) 
        
    def openWorksheet(self, path=False):
        '''
        Opens a saved worksheet from path. If path is not given, the user is
        asked to choose a file in a dialog. It is possible to open more than
        one file at once.
        Afterwards a DOM representation is created and passed to new worksheet(s).
        '''
        
        if not path:
            path = Dialogs.open()
        #if the user didn't choose a path it aborts
        if not path:
            return
        #if only one path was given it has to be enclosed in a list
        elif type(path)==str:
            path = [path]
        
        #now all files get opened
        for p in path:
            DOM = self._parseXML_(p)
            #if not possible to load DOM try the next path
            if not DOM:
                continue
            childDOM = DOM.getElementsByTagName('worksheet')[0]
            self.addWorksheet(childDOM, p)

    
    def saveWorksheet(self, worksheet=None):
        '''
        Saves a worksheet to file. If it has not yet a path
        the saveWorksheetAs method is called.
        
        worksheet can be:
            -a worksheet: this worksheet is saved
            -None: the currently active worksheet is saved
            -'all': all worksheets are saved
        '''
        
        if not worksheet:
            pos = self.get_current_page()
            worksheet = [self.children[pos]]
        elif type(worksheet)==str and worksheet=='all':
            worksheet = self.children
        else:
            worksheet = [worksheet]
        
        for w in worksheet:
            if not w.path:
                if self.saveWorksheetAs(w): continue   
                else: return False
            buf = w.toxml()
            w.setChanged(False)
            f = open(w.path, 'w')
            f.write(buf)
            f.close()
        return True
            
    def saveWorksheetAs(self, worksheet=None):
        '''
        Opens Save-Dialog and saves given or currently opened worksheet
        to the path choosen by the user.
        worksheet can be:
            -a worksheet: this worksheet is saved
            -None: the currently opened worksheet is saved
        '''
        
        if not worksheet:
            pos = self.get_current_page()
            worksheet = self.children[pos]
        
        #TODO: set Default-path
        path = Dialogs.saveAs(worksheet)
        if path:
            buf = worksheet.toxml()
            f = open(path, 'w')
            f.write(buf)
            f.close()
        
            worksheet.setPath(path)
            worksheet.setChanged(False)
            return True
        else: return False
    
    def saveChangedWorksheets(self):
        '''
        Opens a dialog where the user can see all changed worksheets
        and can choose wich ones are to save. If just one worksheet is
        changed, a different dialog appears were the user can just
        choose between yes and no.
        
        This will mostly be used before closing Symbide
        '''
        
        changedWorksheets = []
        worksheets = False
        
        for w in self.children:
            if w.hasChanged():
                changedWorksheets.append(w)
        if len(changedWorksheets)==1:
            worksheets = Dialogs.saveSingle(changedWorksheets[0])
        elif len(changedWorksheets)>1:
            worksheets = Dialogs.saveMulti(changedWorksheets)
        
        if worksheets==None:
            return False
        elif worksheets:
            for w in worksheets:
                if not self.saveWorksheet(w):
                    return False
        return True
                
    def storeWorksheets(self):
        '''
        Stores all currently open worksheets, so they can be reopened when
        starting a new session. When a worksheet has been saved somewhere
        by the user, only the path is stored. Otherwise it is saved into
        the temp_worksheets folder defined in path.xml in the symbide main
        directory.
        '''
        
        #delete previously stored worksheets
        self.deleteStoredWorksheets()
        
        worksheets = []
        path = self.settings.dirs['temp_worksheets']+'worksheet%s.xml'
        for w in self.children:
            if not w.path:
                w.path = path%len(worksheets)
                self.saveWorksheet(w)
            worksheets.append(w.path)
        
        #the worksheet list is stored with pickle
        f = open(self.settings.dirs['temp_worksheets']+'worksheets.pkl','w')
        cPickle.dump(worksheets, f)
    
    def deleteStoredWorksheets(self):
        '''
        Deletes all xml-files in the temp_worksheets directory
        '''
        
        dir = self.settings.dirs['temp_worksheets']
        files = listdir(dir)
        for f in files:
            p = dir+f
            if os.path.isfile(p) and os.path.splitext(f)[1]=='.xml':
                remove(p)
          
    def restoreWorksheets(self):
        '''
        Opens all stored worksheets. If files can't be found they are
        just ignored.
        '''
        tempDir = self.settings.dirs['temp_worksheets']
        
        #try to load infos where worksheets are saved
        try:
            f = open(tempDir+'worksheets.pkl')
            sheets = cPickle.load(f)
        except:
            sheets = []
        
        #get path of all worksheets that still exist
        worksheets = []
        for w in sheets:
            if os.path.exists(w):
                worksheets.append(w)
        
        #get DOM representation and add worksheets to notebook
        for path in worksheets:
            DOM = self._parseXML_(path)
            
            #if not possible to load DOM try the next path
            if not DOM:
                continue
            childDOM = DOM.getElementsByTagName('worksheet')[0]
            
            #if file was saved to temp_worksheets dir, don't give
            #the worksheet the path.
            if os.path.dirname(path)+'/'==tempDir:
                self.addWorksheet(childDOM)
            else:
                self.addWorksheet(childDOM, path)    


class Worksheet(WidgetOrganizer, gtk.ScrolledWindow):
    '''
    A gtk.ScrolledWindow that can hold an arbitraire amount of cells. It
    is, amongs others, responsible for completing the DOM representation.
    
    Actually between the Worksheet and it's cells there is another
    widget: WorksheetVBox(gtk.VBox)
    '''
    
    def __init__(self, ancestor, DOM=None, path=''):
        gtk.ScrolledWindow.__init__(self)
        WidgetOrganizer.__init__(self, 'Worksheet', ancestor)
        
        self.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
        
        self.namespace = {}
        self.path = path #path where it was last saved to or opened from
        self.caption = ''
        self.shortCaption = ''
        self.time = 0 #this is needed for double key events
        
        #create Label shown in Notebook-header
        self.tabWidget = self._createTabWidget()
        
        #create WorksheetVBox-widget
        child = WorksheetVBox(self, DOM)
        self.addChild(child)
        self.add_with_viewport(child)
            
    def _createTabWidget(self):
        '''
        Creates a widget that is shown in the tab of the notebook. It
        is not integrated into the widget tree.
        '''
        
        #TODO:enable shortcuts
        
        caption = ''
        self.shortCaption = ''
        
        #get the captions of all other open worksheets
        captions = []
        for w in self.ancestor.children:
            captions.append(w.caption)
        
        #if there is no path to extract a name from, it gives the
        #worksheet the name 'new1', 'new2' depending on which names
        #are still free 
        if self.path=='':
            i = 0
            while True:
                caption = 'new'+str(i)
                if caption not in captions:
                    break
                i+=1
        else:
            caption = os.path.basename(self.path)
        
        #if the caption is too long a short caption is used
        if len(caption)>9:
            self.shortCaption = caption[:8] + '...'
        
        self.caption = caption
        tabWidget = gtk.HBox()
        
        #create a button to close worksheet
        exitButton = gtk.ToolButton(gtk.STOCK_CLOSE)
        exitButton.show()
        exitButton.connect('clicked',self._event_closeWorksheet)
        
        #create label
        if self.shortCaption:
            label = gtk.Label(self.shortCaption)
        else:
            label = gtk.Label(caption)
        label.show()
        
        
        tabWidget.pack_start(exitButton)
        tabWidget.pack_start(label)
        
        return tabWidget
    
    def _event_closeWorksheet(self, widget):
        '''
        Closes the worksheet.
        '''
        
        self.ancestor.closeWorksheet(self)
        return True
    
    def _event_save(self, *args):
        '''
        Saves the worksheet.
        '''
        
        self.ancestor.saveWorksheet(self)
        return True
    
    def _event_saveAll(self, *args):
        '''
        Saves all currently opened worksheets
        '''
        
        self.ancestor.saveWorksheet('all')
        return True
    
    def _event_saveAs(self, *args):
        '''
        Opens a dialog where the user can choose a path. Afterwards
        saves the worksheet to that path.
        '''
        
        self.ancestor.saveWorksheetAs(self)
        return True
        
    def setPath(self, path):
        '''
        Changes the path variable of the worksheet and updates the tab widget
        '''
        
        self.path = path
        self.tabWidget = self._createTabWidget_()
        self.ancestor.set_tab_label(self, self.tabWidget)
    
    def todom(self, **args):
        '''
        Creates a DOM representation of the whole Worksheet, inclusive
        Document Header.
        '''
        
        DOM = minidom.Document()
        rootElement = WidgetOrganizer.todom(self, *args)
        rootElement.namespaceURI = 'http://code.google.com/p/symbide/0.1'
        DOM.appendChild(rootElement)
        DOM.normalize()
        return DOM
       
       
class WorksheetVBox(WidgetOrganizer, gtk.VBox):
    '''
    A gtk.VBox that is a container for cells, that are separated
    with CellAdders. 
    '''
    
    def __init__(self, ancestor, DOM=None):
        gtk.VBox.__init__(self)
        WidgetOrganizer.__init__(self, 'WorksheetVBox', ancestor)
        
        self.xmlTag = 'worksheet'
        
        #create cellAdder at the end, enabling adding cells per double-click
        cellAdder = CellAdder(self)
        self.addChild(cellAdder)
        self.pack_start(cellAdder, expand=True)
        
        if DOM:
            cells = DOM.getElementsByTagName('cell')
            for cellDOM in cells:
                self.addCell(DOM=cellDOM)
        else:
            self.addCell()
        
        self.changed = False
    
    def addCell(self, DOM=None, neighbor=None, relPos=0):
        '''
        Creates a new cell above the neighbor or if not given,
        at the end of the worksheet.
        ''' 
        
        cell = Cell(self, DOM)
        cellAdder = CellAdder(self)
     
        if neighbor:
            pos = self.children.index(neighbor)+relPos
        else:
            pos = len(self.children)-1
        
        self.addChild(cell, pos)
        self.pack_start(cell, expand=False)
        self.reorder_child(cell, pos)
        
        self.addChild(cellAdder, pos)
        self.pack_start(cellAdder, expand=False)
        self.reorder_child(cellAdder, pos)
        
        self.changed = True
        
        self.set_focus_child(self.children[pos+1])
        #TODO: handle focusing

    def closeCell(self, appendant):
        '''
        Closes a cell and the appendant CellAdder
        '''

        pos = self.children.index(appendant)
        if type(appendant) is Cell:
            pos -= 1       
        self.removeChild(self.children[pos])
        self.removeChild(self.children[pos])
        self.changed = True


class CellAdder(WidgetOrganizer, gtk.EventBox):
    '''
    A gtk.EventBox that enables the user to add new cells through
    double-click on a CellAdder
    '''
    
    def __init__(self, ancestor):
        gtk.EventBox.__init__(self)
        WidgetOrganizer.__init__(self, 'CellAdder', ancestor)
        
        self.connect('button_press_event', self._event_addCell)
        self.connect('enter-notify-event', self._event_mouseEnter)
        self.connect('leave-notify-event', self._event_mouseLeave)
        
        #A label to show on the evnt adder
        l = gtk.Label('_________')
        l.show()
        self.add(l)
    
    def _event_mouseEnter(self, widget, event):
        '''
        Changes the curser when it is over the CellAdder
        '''
        
        cursor = gtk.gdk.PLUS
        gtk.gdk.Window.set_cursor(self.window, gtk.gdk.Cursor(gtk.gdk.PLUS))
        
    def _event_mouseLeave(self, widget, event):
        '''
        Changes the curser back when it leaves the CellAdder
        '''
        
        cursor = gtk.gdk.ARROW
        gtk.gdk.Window.set_cursor(self.window, gtk.gdk.Cursor(gtk.gdk.ARROW))
    
    def _event_addCell(self, widget, event):
        '''
        Creates a new Cell if the user double-clicks the CellAdder
        '''
        if event.type==gtk.gdk._2BUTTON_PRESS:
            self.ancestor.addCell(neighbor=self)
  
               
class Cell(WidgetOrganizer, gtk.Frame):
    '''
    A Cell is only a gtk.Frame that holds a CellVBox.
    '''
    
    def __init__(self, ancestor, DOM=None):
        gtk.Frame.__init__(self)
        WidgetOrganizer.__init__(self, 'Cell', ancestor)
        
        self.set_shadow_type(gtk.SHADOW_NONE)     
        
        #create CellVBox-object
        child = CellVBox(self, DOM)
        self.addChild(child)
        self.add(child)
    
    def evaluate(self):
        '''
        evaluates this cell
        '''
        
        self.children[0].evaluate()

    
class CellVBox(WidgetOrganizer, gtk.VBox):
    '''
    A CellVBox contains an Input and an Output.
    '''
    
    def __init__(self, ancestor, DOM=None):
        gtk.VBox.__init__(self)
        WidgetOrganizer.__init__(self, 'CellVBox', ancestor)
        
        self.xmlTag = 'cell'
        
        if DOM:
            inputDOM = DOM.getElementsByTagName('input')[0]
            outputDOM = DOM.getElementsByTagName('output')[0]
        else:
            inputDOM = None
            outputDOM = None
        
        #create subwidgets
        input = InputFrame(self, inputDOM)
        self.addChild(input)
        self.pack_start(input, expand=False)
        
        output = OutputFrame(self, outputDOM)
        self.addChild(output,1)
        self.pack_start(output, expand=False)
                    
    def setdom(self, DOM):
        '''
        Replaces the current Output with the Output specified in the DOM.
        '''
        
        self.children[1].setdom(DOM)
    '''    
    def todom(self):
        #TODO:Find out why I implemented this
        DOM = minidom.Element(self.xmlTag)
        for child in self.children:
            childDOM = child.todom()
            if childDOM:
                childDOM.parentNode = None
                DOM.appendChild(childDOM)
        return DOM
'''
    
class InputFrame(WidgetOrganizer, gtk.Frame):
    '''
    Just a gtk.Frame around the Input
    '''
    
    def __init__(self, ancestor, DOM=None):
        gtk.Frame.__init__(self, 'In')
        WidgetOrganizer.__init__(self, 'InputFrame', ancestor)
        
        #create Input-object
        child = Input(self, DOM)
        self.addChild(child)
        self.add(child)
        
        
class OutputFrame(WidgetOrganizer, gtk.Frame):
    '''
    Just a gtk.Frame around the Output
    '''
    
    def __init__(self, ancestor, DOM=None):
        gtk.Frame.__init__(self, 'Out')
        WidgetOrganizer.__init__(self, 'OutputFrame', ancestor)
        
        #create Output-widget
        child = Output(self, DOM)
        self.addChild(child)
        self.add(child)
        
    def setdom(self, DOM):
        '''
        Replaces the current Output with the Output specified in the DOM.
        '''
        
        self.children[0].setdom(DOM)

        
class InputBase(WidgetOrganizer):
    '''
    This class implements all things that can be done by gtk.TextView as
    well as gtksourceview. Both inherit from this class and only have to
    add their special methods. But the whole core of the Output is
    implemented here.
    
    Attention: a buffer has to be instantiated before calling the
               __init__ method.
    '''
    
    def __init__(self, ancestor, DOM=None):
        WidgetOrganizer.__init__(self, 'Input', ancestor)
        
        #sets the style of the text
        #TODO: let the styles be set from the user
        tag = self.buffer.create_tag('global')
        tag.set_property('size-points',10)
        self.updateTags()
        
        self.connect('backspace', self._event_closeCell)
        self.connect('key_press_event', self._event_keyPress)
        self.buffer.connect('changed', self._event_changed)
        self.namespace = self.getAncestor('Worksheet').namespace
        self.time = 0

        if DOM: self.setdom(DOM)
        self.changed = False
        
    def _event_closeCell(self, widget):
        '''
        Closes the cell if the following is True:
            -Input is empty
            -Double-backspace-click in a defined period 
        '''
        
        #don't close if input-cell not empty
        if self.get_buffer().get_char_count() is not 0:
            return False
            
        #only react to double backspace:
        t = time()
        dt = t-self.time
        if dt<0.1 or dt>0.4:
            self.time = t
            return False
        
        #remove cell and appendant cellAdder from WorksheetVBox
        self.getAncestor('WorksheetVBox').closeCell(self.getAncestor('Cell'))
        return True
    
    def _event_keyPress(self, widget, event):
        '''
        Reacts to shortcuts for evaluateCell, undo, redo.
        '''
        
        key = event.keyval
        masks = gtk.accelerator_get_default_mod_mask()
        
        events = (('evaluateCell', 'evaluate'),
                  ('undo', 'undo'),
                  ('redo', 'redo'))
        
        for i, j in events:
            keyval, modifier = self.settings.shortcuts[i]
            if key==keyval and masks & event.state==modifier:
                getattr(self, j)()
                return True
    
    def _event_changed(self, buffer):
        '''
        Update the styling tags in buffer, and set changed True
        '''
        
        self.updateTags()
        self.changed = True
    
    def updateTags(self):
        '''
        Update the styling tags in buffer.
        '''
        
        start, end = self.buffer.get_bounds()
        self.buffer.apply_tag_by_name('global', start, end)
        
    def undo(self):
        '''
        Undos the last user action in buffer. Has to be overridden!
        '''
        
        pass
        
    def redo(self):
        '''
        Redos the last user action in buffer. Has to be overridden!
        '''
        
        pass
    
    def todom(self, *args):
        '''
        Returns a DOM representation of Input.
        '''
        
        text = self.getText()
        DOM = minidom.Element('input')
        DOM_text = minidom.Document().createTextNode(escape(text))
        DOM.appendChild(DOM_text)
        return DOM 
    
    def getText(self):
        '''
        Returns the entire text of the buffer.
        '''
        
        start, end = self.buffer.get_bounds()
        text = self.buffer.get_text(start, end)
        return text
    
    def setdom(self, DOM):
        '''
        Replaces the current Input with the Input represented in the DOM.
        '''
        
        if DOM.childNodes:
            text = DOM.childNodes[0].data
            self.buffer.set_text(unescape(text))
        
    def evaluate(self):
        '''
        Evaluates the text in the Input buffer.
        '''
         
        text = self.getText()
        DOM = self.executor.execute(text, self.namespace)
        self.getAncestor('CellVBox').setdom(DOM)


class InputSourceView(InputBase, gtksourceview.SourceView):
    '''
    This class provides syntax highlighting and undo redo functionality
    and is all in all a nice editor.
    '''
    
    def __init__(self, ancestor, DOM=None):
        self.buffer = gtksourceview.SourceBuffer()
        gtksourceview.SourceView.__init__(self, self.buffer)
        InputBase.__init__(self, ancestor, DOM)
        
        manager = gtksourceview.SourceLanguagesManager()
        language = manager.get_language_from_mime_type("text/x-python")
        
        self.buffer.set_language(language)
        self.buffer.set_highlight(True)
        
        #self.set_show_line_numbers(True)
        self.set_smart_home_end(True)
        #self.set_show_margin(True)
        self.set_tabs_width(int(self.settings.options['tabswidth']))
        self.set_auto_indent(True)
        
    def undo(self):
        '''
        undos the last user action in buffer.
        '''
        
        if self.buffer.can_undo():
            self.buffer.undo()
        
    def redo(self):
        '''
        redos the last user action in buffer.
        '''
        
        if self.buffer.can_redo():
            self.buffer.redo()


class InputTextView(InputBase, gtk.TextView):
    '''
    This is only for user, who don't have gtksourceview installed.
    '''
    
    def __init__(self, ancestor, DOM):
        self.buffer = gtk.TextBuffer()
        gtk.TextView.__init__(self, self.buffer)
        InputBase.__init__(self, ancestor, DOM)


class OutputBase(WidgetOrganizer):
    '''
    This class implements all things that can be done by gtk.TextView as
    well as gtksourceview. Both inherit from this class and only have to
    add their special methods. But the whole core of the Output is
    implemented here.
    
    Attention: a buffer has to be instantiated before calling the
               __init__ method.
    '''
    
    def __init__(self, ancestor, DOM=None):
        WidgetOrganizer.__init__(self, 'Output', ancestor)
        
        self.buffer.connect('changed', self._event_changed)
        self.connect('key_press_event', self._event_keyPress)
        
        self.set_editable(False)
        tag = self.buffer.create_tag('global')
        tag.set_property('size-points',10)
        self.updateTags()
        
        self.time = 0
        self.objects = []
        self.setdom(DOM)
        self.changed = False    
    
    def _event_keyPress(self, widget, event):
        '''
        Reacts to different key combinations:
            -if control is pressed it does nothing, because
                it should be possible to copy things.
            -if backspace is pressed twice in a short interval
                the Output is closed
            -if any other 'normal' key is pressed a new cell
                is created and the selected text is copied inside
        '''
        
        if event.state & gtk.gdk.CONTROL_MASK:
            return False
        
        if event.keyval==ord('\b'):
            t = time()
            dt = t-self.time
            self.time = t
            print dt
            if 0.1<dt<0.4:
                self._closeOutput()
            return True
        
        if 33<event.keyval<127 or event.keyval==ord('\n'):
            self._addCell()    
            return True
    
    def _event_changed(self, buffer):
        '''
        Update the styling tags in buffer, and set changed True.
        '''
        
        self.changed = True
        self.updateTags()
    
    def _addCell(self):
        '''
        Adds a new cell under the own cell to the worksheet.
        Any text in the output that is highlighted, is copied into
        the input of the new cell. 
        '''
        
        parentCell = self.getAncestor('Cell')
        text = self.getText()
        
        DOM = minidom.Element('cell')
        
        DOM_input = minidom.Element('input')
        DOM_text = minidom.Document().createTextNode(escape(text))
        DOM_input.appendChild(DOM_text)
        
        DOM_output = minidom.Element('output')
        DOM.appendChild(DOM_input)
        DOM.appendChild(DOM_output)
        
        self.getAncestor('WorksheetVBox').addCell(DOM, neighbor=parentCell, relPos=1)
        
    def _closeOutput(self):
        '''
        Removes this Output and it's outputFrame
        '''
        
        self.getAncestor('CellVBox').removeChild(self.ancestor)
    
    def _deletePics(self):
        files = listdir(self.settings.dirs['temp_pictures'])
        print 'delet Pics'
        for f in files:
            p = self.settings.dirs['temp_pictures']+f
            if os.path.isfile(p) and os.path.splitext(f)[1]=='.png':
                remove(p)
        
    def destroy(self):
        '''
        Destroys all objects stored in self.objects and deletes the
        stored pictures in the 'temp_pictures' directory'.
        
        This is necessary because the Plot-Windows run their
        own windows and Threads.
        '''
        
        for i in self.objects:
            i.destroy()
        del self.objects
        self._deletePics()
        gtksourceview.SourceView.destroy(self)
    
    def updateTags(self):
        '''
        Update the styling tags in buffer.
        '''
        
        start, end = self.buffer.get_bounds()
        self.buffer.apply_tag_by_name('global', start, end)
    
    def hasChanged(self):
        '''
        Returns True if itself or any of the objects in self.objects
        have changed.
        '''
        
        return any([i.changed for i in self.objects]+[self.changed])
    
    def setChanged(self, changed):
        '''
        Sets the changed attribut of itself and the objects in
        self.objects
        '''
        
        for i in self.objects+[self]:
            i.changed = changed
    
    def getText(self):
        '''
        Returns the selected text, or if nothing is selected, the
        entire text of the buffer.
        '''
        
        
        self.set_editable(True) #strange, but this has to be done!
        bounds = self.buffer.get_selection_bounds()
        self.set_editable(False)
        
        if bounds:
            start, end = bounds
        else:
            start, end = self.buffer.get_bounds()
        text = self.buffer.get_text(start, end)
        return text
    
    def todom(self):
        '''
        Returns a DOM representation of Output.
        '''

        plotNodes = self.DOM.getElementsByTagName('symbide_plot')
        assert len(plotNodes)==len(self.objects)
        for i in range(len(self.objects)):
            self.DOM.replaceChild(self.objects[i].todom(), plotNodes[i])
        
        return self.DOM
    
    def setdom(self, DOM):
        '''
        Replaces the current Output with the Output represented he DOM.
        '''
        
        #if Output empty, turns itself invisible
        self.buffer.set_text('')
        
        if not DOM or not DOM.childNodes:
            self.DOM = minidom.Element('output')
            self.ancestor.hide_all()
            return
        
        #take DOM out of the DOM tree
        if DOM.parentNode:
            DOM.parentNode.removeChild(DOM)
            
        self.DOM = DOM
        
        #some shortcuts
        end = self.buffer.get_end_iter
        append = lambda text:self.buffer.insert(end(), text)
        self.objects = []
        for node in DOM.childNodes:
            if node.nodeType==node.TEXT_NODE:
                append(unescape(node.nodeValue))
            elif node.nodeType==node.ELEMENT_NODE:
                if node.localName=='symbide_error':
                    if node.childNodes:
                        s = node.childNodes[0].nodeValue
                    else:
                        s = ''
                    append('error: %s\n'%s)
                elif node.localName=='symbide_plot':
                    plotWidget = Plot(node, self.settings, self.parsers)
                    self.objects.append(plotWidget)
                    anchor = self.buffer.create_child_anchor(end())
                    self.add_child_at_anchor(plotWidget, anchor)
                
        self.ancestor.show_all()
                


class OutputSourceView(OutputBase, gtksourceview.SourceView):
    '''
    This class provides syntax highlighting and undo redo functionality
    and is all in all a nice editor.
    '''
    
    def __init__(self, ancestor, DOM=None):
        self.buffer = gtksourceview.SourceBuffer()
        gtksourceview.SourceView.__init__(self, self.buffer)
        OutputBase.__init__(self, ancestor, DOM)
        
        manager = gtksourceview.SourceLanguagesManager()
        language = manager.get_language_from_mime_type("text/x-python")
        
        self.set_tabs_width(int(self.settings.options['tabswidth']))
        self.buffer.set_language(language)
        self.buffer.set_highlight(True)


class OutputTextView(OutputBase, gtk.TextView):
    '''
    This is only for user, who don't have gtksourceview installed.
    '''
    
    def __init__(self, ancestor, DOM=None):
        self.buffer = gtk.TextBuffer()
        gtk.TextView.__init__(self, self.buffer)
        OutputBase.__init__(self, ancestor, DOM)


if flags['gtksourceview']:
    Output = OutputSourceView
    Input = InputSourceView
else:
    Output = OutputTextView
    Input = InputTextView